package pool;


import configuration.MyConfiguration;
import exception.ReadTimeOutSleepTime;
import handler.*;
import http.HttpMethod;
import http.MyHttpRequest;
import http.MyHttpResponse;
import util.MyArrays;
import util.MyUtil;
import util.reg.RegEx;
import util.reg.RegFactory;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * @author Karl
 * @Date 2023/1/1 - 20:22
 */
public class ReadRequestTask implements Runnable {

    private static String lineSeparator = System.lineSeparator();
    private static Charset utf_8 = StandardCharsets.UTF_8;
    private static byte[] lineByte = lineSeparator.getBytes(utf_8);
    private static byte[] lineByte2 = (lineSeparator + lineSeparator).getBytes(utf_8);

    private Socket localSocket;
    private WriteTask writeTask;
    private InputStream lin;
//    private static String headLineReg = "[A-Z]{3,7}\\s+[^\\s]+\\s+HTTP\\/[0-9]\\.[0-9]";


    public ReadRequestTask(Socket socket) throws SocketException {
        localSocket = socket;
//        localSocket.setSoTimeout(10000);
    }

    @Override
    public void run() {

        MyHttpRequest request = null;
        //获取远程socket的地址，然后进行打印
        String addr = localSocket.getRemoteSocketAddress().toString();
        MyUtil.log("\n\nprocess one socket : %s", addr);
        try {

            lin = localSocket.getInputStream();

            int sleepTime = 100;
            int off = 0, status = -1, now = -1;
            String line = "";
            byte[] buf = new byte[8192];
            RegEx lineReg = RegFactory.createRequestLineReg();
            while (!localSocket.isClosed()) {
                // 用来记录当前在读取什么部分
                status = -1;
                request = new MyHttpRequest();
                int requestOrder = 0;
                boolean flag = true;
                lineReg.reset();
                while (flag) {
                    while (requestOrder == 0 && (now = lin.read()) != -1) {
                        sleepTime = 50;
                        //找到请求行
                        if ((line = lineReg.enterChar((byte) now)) != null) {
                            request.setRequestLine(line);
                            requestOrder++;
                            //进入找请求体的时候，不确定长度

                            //找请求头结束标志CRLF CRLF
                            while (requestOrder == 1 && (now = lin.read()) != -1) {
                                buf[off++] = (byte) now;
                                if (now == lineByte2[status + 1]) {
                                    status++;
                                    if (status == 3) {
                                        status = -1;
                                        off = 0;
                                        break;
                                    } else if (status == 1) {
                                        request.addRequestHeader(new String(buf, 0, off));
                                        off = 0;
                                    }
                                } else {
                                    status = -1;
                                    if (now == lineByte2[status + 1]) {
                                        status++;
                                    }
                                }
                            }

                            //判断是否有请求体，只有POST才判断一下有没有
                            //判断有多少请求体，并接收
                            receiveBody(request, lin);
                            flag = false;

                            if (!offerNewRequest(request)) {
                                return;
                            }
                            //第一次是connect方法，直接转发就好了
                            if (request.getMethod().equals(HttpMethod.CONNECT)) {
                                OutputStream rout = writeTask.getRemote().getOutputStream();
                                while ((off = lin.read(buf)) > 0) {
                                    rout.write(buf, 0, off);
                                }
                                return;
                            }
                        }
                    }
                    if (flag && now == -1) {
                        Thread.sleep(sleepTime);
                        sleepTime += 10;
                        if (sleepTime > 600) return;
                    }

                }
            }

        } catch (Exception e) {
            MyUtil.log("exception : %s %s", e.getClass(), e.getLocalizedMessage());
//            e.printStackTrace();
        } finally {
            MyUtil.log("close socket, system cleanning ...  %s ", addr);
            MyUtil.closeIo(localSocket);
            if (writeTask != null) {
                MyUtil.closeIo(writeTask.getRemote());
            }
        }

    }

    private void receiveBody(MyHttpRequest request, InputStream lin) throws IOException {

        int now = -1, len = -1;
        //开始接收POST的请求体
        if ((len = request.getContentLength()) != -1) {
            while (len > 0) {
                while (len > 0 && (now = lin.read()) != -1) {
                    request.addRequestBody((byte) now);
                    len--;
                }

            }
            return;
        } else if (request.isChunked()) {
            //通过chunked读取响应体
            len = getChunkedLen(lin, request);
            while (len > 0) {
                while (len > 0) {
                    if ((now = lin.read()) != -1) {
                        request.addRequestBody((byte) now);
                        len--;
                        if (len < 2)
                            System.out.println();
                    }
                }
                //写死，读完之后一定是CRLF
                readCRLF(lin, request);
                len = getChunkedLen(lin, request);

            }
            readCRLF(lin, request);
        }

    }


    /**
     * 读取掉is接下来的两个字节，而且必须是\r\n
     *
     * @param is      目标服务器的inputStream
     * @param request 这次请求的HTTP对象
     * @throws IOException 如果读到的不是\r\n就抛异常，结束此次连接
     */
    private void readCRLF(InputStream is, MyHttpRequest request) throws IOException {
        int now = -1;
        while (true) {
            if ((now = is.read()) > 0) {
                request.addRequestBody((byte) now);
                if (now != lineByte[0]) throw new IOException("chunked后不是换行\\r");
                break;
            }
        }
        while (true) {
            if ((now = is.read()) > 0) {
                request.addRequestBody((byte) now);
                if (now != lineByte[1]) throw new IOException("chunked后不是换行\\n");
                break;
            }
        }
    }

    /**
     * 读取chunked的首行，即一个十六进制的数表示接下来消息块的长度
     *
     * @param is      客户端的inputStream
     * @param request 这次请求的HTTP对象
     * @return 16机制转成十进制的字节数
     * @throws IOException
     */

    private int getChunkedLen(InputStream is, MyHttpRequest request) throws IOException {
        int status = -1, off = 0, now = -1, len = -1, sleepTime = 50;
        byte[] buf = new byte[32];
        while (true) {
            if ((now = is.read()) != -1) {
                sleepTime = 50;
                buf[off++] = (byte) now;
                request.addRequestBody((byte) now);
                if (now == lineByte[status + 1]) {
                    status++;
                    if (status == 1) {
                        len = Integer.parseInt(new String(buf, 0, off).trim(), 16);
                        return len;
                    }
                } else {
                    status = -1;
                    if (now == lineByte[status + 1]) {
                        status++;
                    }
                }
            } else {
                try {
                    Thread.sleep(sleepTime);
                    sleepTime += 10;
                    if (sleepTime > 450) throw new ReadTimeOutSleepTime("在getChunkedLen读取超时");
                } catch (InterruptedException e) {
                    return 0;
                }

            }
        }
    }


    /**
     * 处理新请求，看是否需要重定向或者过滤，过滤就然会
     *
     * @param request 新添加的请求
     */
    private boolean offerNewRequest(MyHttpRequest request) {
        //请求行和请求头结束了，判断一下是否需要重定向或者过滤
        if (RedirectJudge.needRedirect(request)) {
            RedirectJudge.redirect(request);
        }

        try {
            //是否是第一次请求，是的话开启写回的线程
            if (writeTask == null) {
                writeTask = new WriteTask(localSocket, request);
                ThreadManager.getManager().execute(writeTask);
            } else {
                if (!writeTask.addRequest(request)) {
                    MyUtil.log("exception %s", "offer into linkedQueue exception");
                }
                //在添加到request中时，会去发送请求到remote
            }
        } catch (IOException e) {
            //开启工作线程失败，说明该请求的目标地址不可带，写回去404
            //返回响应报文
            MyUtil.log("exception : %s %s", e.getClass(), e.getLocalizedMessage());
            return false;
        }
        return true;
    }


}

